using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Acms.Bll
{
    public abstract class BaseItem
    {
        protected BaseItem()
        {
            isNewRecord = true;
        }

        protected BaseItem(Hashtable table)
        {
            Load(table);
            concurencyId = LoadConcurencyId(table);
        }

        protected BaseItem(Hashtable table, ICollection collection)
        {
            Load(table);
            concurencyId = LoadConcurencyId(table);
            this.collection = collection;
        }

        protected abstract void Load(Hashtable table);

        private ICollection collection;
        protected ICollection Collection
        {
            get { return collection; }
        }

        private object concurencyId;
        public object ConcurencyId
        {
            get { return concurencyId; }
        }

        protected void SetVersionId(object newVersionId)
        {
            concurencyId = newVersionId;
        }

        private bool isNewRecord;
        public bool IsNewRecord
        {
            get { return isNewRecord; }
        }

        protected virtual void Updated(object versionId)
        {
            this.isNewRecord = false;
            this.concurencyId = versionId;
        }

        protected virtual void Deleted()
        {
        }

        private Guid transactionKey;
        public Guid TransactionKey
        {
            get { return transactionKey; }
        }

        public void UseTransaction(Guid key)
        {
            transactionKey = key;
        }

        public void ClearTransactionKey(Guid key)
        {
            if (transactionKey == key)
            {
                transactionKey = Guid.Empty;
            }
        }

        public void BeginTransaction()
        {
            BeginTransaction(CreateDal());
        }

        protected void BeginTransaction(Acms.SqlDal.Base dal)
        {
            if (transactionKey != Guid.Empty)
            {
                throw new ApplicationException("This class is already using a transaction");
            }
            else
            {
                transactionKey = dal.StartTransaction();
            }
        }

        public void CommitTransaction()
        {
            CommitTransaction(CreateDal());
        }

        protected void CommitTransaction(Acms.SqlDal.Base dal)
        {
            if (transactionKey == Guid.Empty)
            {
                throw new ApplicationException("This class is not using a transaction");
            }
            else
            {
                dal.CommitTransaction(transactionKey);
                this.transactionKey = Guid.Empty;
            }
        }

        public void RollbackTransaction()
        {
            RollbackTransaction(CreateDal());
        }

        protected void RollbackTransaction(Acms.SqlDal.Base dal)
        {
            if (transactionKey != Guid.Empty)
            {
                dal.RollbackTransaction(transactionKey);
                this.transactionKey = Guid.Empty;
            }
        }

        protected abstract Acms.SqlDal.Base CreateDal();

        protected virtual object SaveChanges(ref object concurencyId, SqlDal.Base dal)
        {
            throw new NotImplementedException("Method Save Changes must be overriden if Update is called");
        }

        protected void Update()
        {
            Update("", Change.ChangeTypes.Unknown);
        }

        protected void Update(string description, Change.ChangeTypes changeType)
        {
            SqlDal.Base dal = CreateDal();
            bool commit = false;
            object key = null;
            //use transaction if there is one
            if (TransactionKey != Guid.Empty)
            {
                dal.UseTransaction(TransactionKey);
            }
            else if ((Changes != null && Changes.Count > 0) || IsNewRecord)
            {
                //if changes start new tran if not already started
                if (TransactionKey == Guid.Empty)
                {
                    commit = true;
                    BeginTransaction(dal);
                }
            }
            try
            {
                key = SaveChanges(ref concurencyId, dal);
            }
            catch
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction();
                }
                throw;
            }
            //save changes
            try
            {
                if (IsNewRecord && key != null && changeType != Change.ChangeTypes.Unknown)
                {
                    Change c = new Change();
                    c.Action = Change.Actions.Created;
                    c.Description = description;
                    c.Key = key.ToString();
                    c.Type = changeType;
                    c.UserId = GetUserId();
                }
                else if (Changes != null && Changes.Count > 0)
                {
                    foreach (Change c in Changes)
                    {
                        c.Save(TransactionKey);
                    }
                }
            }
            catch
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction(dal);
                }
                throw;
            }
            //commit tran if started above
            if (commit)
            {
                CommitTransaction(dal);
            }
            Updated(ConcurencyId);
        }

        protected virtual void DeleteRecord(object concurencyId, SqlDal.Base dal)
        {
            throw new NotImplementedException("Method DeleteRecord must be overriden if Delete is called");
        }
        protected void Delete()
        {
            Delete(null, null, 0);
        }

        protected void Delete<K>(Change.ChangeTypes? changeType, string description, K key) where K : struct
        {
            SqlDal.Base dal = CreateDal();
            bool commit = false;
            Change change = null;
            if (TransactionKey != Guid.Empty)
            {
                dal.UseTransaction(TransactionKey);
            }
            else if (changeType != null)
            {
                if (TransactionKey == Guid.Empty)
                {
                    BeginTransaction(dal);
                    commit = true;
                }
                change = new Change() { Action = Change.Actions.Deleted, Created = DateTime.Now, Key = key.ToString(), Description = description, Type = changeType.Value, UserId = GetUserId() }; 
            }
            DeleteRecord(ConcurencyId, dal);
            if (change != null)
            {
                //save change
            }
            if (commit)
            {
                CommitTransaction(dal);
            }
            Deleted();
        }

        public bool CheckConcurent(object concurencyId)
        {
            return CheckConcurent(concurencyId, true);
        }

        public bool CheckConcurent(object concurencyId, bool throwException)
        {
            bool isConcurrent = false;
            if (this.ConcurencyId == null && concurencyId == null)
            {
                isConcurrent = true;
            }
            else if ((this.ConcurencyId == null && concurencyId != null) || (this.ConcurencyId != null && concurencyId == null))
            {
                isConcurrent = false;
            }
            else if (concurencyId is byte[] && this.ConcurencyId is byte[])
            {
                byte[] a = (byte[])this.ConcurencyId;
                byte[] b = (byte[])concurencyId;
                if (a.Length == b.Length)
                {
                    isConcurrent = true;
                    for (int i = 0; i < a.Length; i++)
                    {
                        if (a[i] != b[i])
                        {
                            isConcurrent = false;
                            break;
                        }
                    }
                }
            }
            else
            {
                isConcurrent = this.ConcurencyId.Equals(concurencyId);
            }

            if (!isConcurrent && throwException)
                throw new Acms.Exceptions.ConcurencyException();

            return isConcurrent;
        }

        protected object LoadConcurencyId(Hashtable table)
        {
            if (table.ContainsKey(SqlDal.Base.BaseFields.VersionId))
            {
                return table[SqlDal.Base.BaseFields.VersionId];
            }
            else
            {
                return null;
            }
        }

        internal static void SortList<T>(List<T> list, string expression, BaseComparison<T> comparison)
        {
            if (expression != null)
            {
                expression = expression.Trim();
            }
            if (!string.IsNullOrEmpty(expression))
            {
                bool globalAssending = true;
                //if form is (field1 asc, field2 desc) desc need to revers internal orders
                if (expression.StartsWith("(") && expression.ToLower().EndsWith(") desc"))
                {
                    globalAssending = false;
                    expression = expression.Substring(1, expression.Length - 7);
                }
                else if (expression.StartsWith("(") && expression.ToLower().EndsWith(")"))
                {
                    expression = expression.Substring(1, expression.Length - 2);
                }

                string[] tmpArr = expression.Split(',');
                for (int i = tmpArr.Length - 1; i >= 0; i--)
                {
                    string t = tmpArr[i].Trim();
                    string field = null;
                    bool ascending = true;
                    if (t.ToLower().EndsWith(" desc"))
                    {
                        field = t.Substring(0, t.Length - 5);
                        ascending = false;
                    }
                    else if (t.ToLower().EndsWith(" asc"))
                    {
                        field = t.Substring(0, t.Length - 4);
                    }
                    else
                    {
                        field = t;
                    }
                    //switch if gloabal asscending is false
                    if (!globalAssending)
                    {
                        ascending = !ascending;
                    }
                    comparison.Set(field, ascending);
                    list.Sort(comparison);
                }
            }
        }

        private List<Change> Changes;
        protected void LogChange<T, K>(T oldValue, T newValue, Change.ChangeTypes changeType, string description, K key) where K : struct
        {
            if (!this.IsNewRecord && oldValue != null && !oldValue.Equals(newValue))
            {
                if (Changes == null)
                {
                    Changes = new List<Change>();
                }
                Changes.Add(new Change() { Created = DateTime.Now, Type = changeType, Action = Change.Actions.Updated, From = oldValue != null ? oldValue.ToString() : null, To = newValue != null ? newValue.ToString() : null, Key = key.ToString(), Description = description, UserId = GetUserId() });
            }
        }

        protected bool HasChange(string key)
        {
            return Changes.Exists(x => x.Key.Equals(key, StringComparison.CurrentCultureIgnoreCase));
        }

        protected int GetUserId()
        {
            int userId = 0;
            Acms.Bll.UserPrinciple principle = null;
            if (System.Web.HttpContext.Current != null)
            {
                principle = System.Web.HttpContext.Current.User as Acms.Bll.UserPrinciple;
            }
            else
            {
                principle = System.Threading.Thread.CurrentPrincipal as Acms.Bll.UserPrinciple;
            }
            if (principle != null && principle.Identity != null && principle.Identity.User != null)
            {
                userId = principle.Identity.User.Id;
            }
            else
            {
                //throw new ApplicationException("Could not find loged in user");
            }
            return userId;
        }
    }
}
